// 定义文本模式  ：状态表
const TextModes = {
    DATA: 'DATA',
    RCDATA: 'RCDATA',
    RAWTEXT: 'RAWTEXT',
    CDATA: 'CDATA',
    INTERPOLATION: 'INTERPOLATION'
}

function decodeHtml (content) {
    // 涉及 HTML实体  命名字符引用  数字字符引用内容  略

    return content
}

function parseComment (context) {
    context.advanceBy("<!--".length)
    closeIndex = context.source.indexOf("-->")

    const content = context.source.slice(0, closeIndex)
    context.advanceBy(content.length)
    context.advanceBy('-->'.length)

    return {
        type: 'Comment',
        content
    }
}

function isEnd (context, ancestors) {
    // 模板内容解析完后 结束
    if (!context.source) return true;

    // // 获取栈顶元素  即父级标签节点
    // const parent = ancestors[ancestors.length - 1]

    // // 匹配到对应的结束标签  则结束
    // if (parent && context.source.startsWith(`</${parent.tag}`)) {
    //     return true;
    // }

    // 优化1 处理多余的内容
    for (let i = ancestors.length - 1; i >= 0; i--) {
        // 只要栈中有同名的开始标签 就结束状态机
        if (context.source.startsWith(`</${ancestors[i].tag}`)) {
            return true;
        }
    }


    return false;
}

function parseCDATA (node, ancestors) {
}

function parseInterpolation (context) {
    context.advanceBy("{{".length)
    closeIndex = context.source.indexOf("}}")
    if (closeIndex < 0) {
        console.error('[90m [  ]-72 [0m', '插值缺少结束定界符')
    }
    const content = context.source.slice(0, closeIndex)
    context.advanceBy(content.length + 2)

    return {
        type: 'Interpolation',
        content: {
            type: 'Expression',
            content: decodeHtml(content)
        }
    }
}

function parseText (context) {
    // 文本内容结尾所索引，默认将 剩余的内容作为文本内容
    let endIndex = context.source.length

    // 找到< 索引
    const ltIndex = context.source.indexOf("<")

    // 寻找 定界符 {{ 索引
    const delimiterIndex = context.source.indexOf("{{")

    if (ltIndex > -1 && ltIndex < endIndex) {
        endIndex = ltIndex;
    }

    if (delimiterIndex > -1 && delimiterIndex < endIndex) {
        endIndex = delimiterIndex;
    }

    const content = context.source.slice(0, endIndex)

    context.advanceBy(content.length)

    return {
        type: 'TEXT',
        content
    }


}


function parseAttributes (context) {
    const { advanceBy, advanceSpaces } = context;
    const props = []

    while (
        !context.source.startsWith('>') &&
        !context.source.startsWith('/>')
    ) {
        // 用于匹配属性名称
        const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source)

        // 属性名
        const name = match[0]
        // 消费属性名
        advanceBy(name.length)
        advanceSpaces()
        // 消费等于号
        advanceBy(1)
        // 消费等于号和值中间的空白字符
        advanceSpaces()

        // 属性值
        let value = '';

        // 获取第一个字符 看是双引号 还是单引号 或字符
        const quote = context.source[0]
        const isQuote = quote === '"' || quote === "'"

        if (isQuote) {
            advanceBy(1)
            // 获取下一个引号的索引
            const endQuoteIndex = context.source.indexOf(quote)
            if (endQuoteIndex > -1) {

                value = context.source.slice(0, endQuoteIndex)
                advanceBy(value.length)
                advanceBy(1)
            } else {
                console.error('[90m [  ]-72 [0m', '属性值缺少引号')
            }
        } else {
            // 没有引号的值
            // 下一个空白字符之前的所有内容作为属性值
            const match = /^[^\t\r\n\f >]+/.exec(context.source)
            value = match[0]
            advanceBy(value.length)
        }
        advanceSpaces()

        props.push({
            type: 'Attribute',
            name,
            value
        })
    }

    return props

}

function parseTag (context, type = 'start') {
    const { advanceBy, advanceSpaces } = context;

    // 获取开始或结束的正则表达式
    const match = type === 'start'
        ? /^<([a-z][^\t\r\n\f />]*)/i.exec(context.source)
        : /^<\/([a-z][^\t\r\n\f />]*)/i.exec(context.source)

    // 匹配成功后 正则表达式 第一个捕获组的值就是标签名称
    const tag = match[1]
    // 消费 正则匹配到的所有内容 如<div
    advanceBy(match[0].length)
    // 消费空白字符
    advanceSpaces()

    // 解析属性
    const props = parseAttributes(context)


    // 在消费 匹配内容后， 如果剩余的字符串是以/> 开头，则说明是自闭和标签
    const isSelfClosing = context.source.startsWith('/>')
    advanceBy(isSelfClosing ? 2 : 1)

    return {
        type: 'Element',
        // 标签名
        tag,
        props,
        // 暂时为空
        children: [],
        // 是否自闭合
        isSelfClosing,
    }
}


function parseElement (context, ancestors) {
    // 解析开始标签
    const element = parseTag(context);

    // 如果是自闭合标签  则返回元素
    if (element.isSelfClosing) {
        return element;
    }

    // 切换正确的文本模式
    if (element.tag === 'textarea' || element.tag === 'title') {
        context.mode = TextModes.RCDATA;
    } else if (/style|xmp|iframe|noembed|noframes|noscript/.test(element.tag)) {
        context.mode = TextModes.RAWTEXT;
    } else {
        context.mode = TextModes.DATA;
    }


    ancestors.push(element);
    // 递归处理子节点的解析
    element.children = parseChildren(context, ancestors);
    ancestors.pop();

    // // 解析结束标签
    // parseTag('end')

    if (context.source.startsWith(`</${element.tag}`)) {
        parseTag(context, 'end')
    } else {
        // 缺少闭合标签
        console.error('[90m [  ]-72 [0m', '标签缺少闭合标签')
    }


    return element;
}

function parseChildren (context, ancestors) {
    // 存储子节点，作为函数返回值
    let nodes = []

    const { mode, source } = context

    // isEnd的时候就表示解析结束
    while (!isEnd(context, ancestors)) {
        let node;



        // 只有DATA模式 和RCDATA模式 才支持插值节点的解析
        if (mode === TextModes.DATA || mode === TextModes.RCDATA) {

            // DATA模式才支持 标签节点解析
            if (mode === TextModes.DATA && source[0] === '<') {
                if (source[1] === '!') {
                    if (source.startsWith('<!--')) {
                        //注释节点
                        node = parseComment(context)

                    } else if (source.startsWith('<![CDATA[')) {
                        // CDATA
                        node = parseCDATA(context, ancestors)
                    }
                } else if (source[1] === '/') {

                    //状态机遇到 闭合标签 就抛出错误，因为缺少对于的开始标签

                    console.error('[90m [  ]-72 [0m', '无效的结束标签')
                    continue;

                } else if (/[a-z]/i.test(source[1])) {
                    // 标签
                    node = parseElement(context, ancestors)
                }
            } else if (source.startsWith("{{")) {
                // 解析插值
                node = parseInterpolation(context)
            }
        }

        // 说明是文本节点
        if (!node) {
            node = parseText(context);
        }

        nodes.push(node)

    }

    return nodes;
}

// 解析器函数 接收模板作为参数
function parse (str) {
    // 上下文对象
    const context = {
        // 模板内容
        source: str,
        // 解析器当前模式 初始化为DATA
        mode: TextModes.DATA,

        // 消费指定的字符
        advanceBy (num) {
            context.source = context.source.slice(num)
        },
        // 消费无用的空白字符
        advanceSpaces () {
            const match = /^[\t\r\n\f ]+/.exec(context.source)
            if (match) {
                context.advanceBy(match[0].length)
            }
        },
    }

    // 函数作用：返回解析后的子节点
    // 参数1 上下文对象 参数2 父节点构成的 节点栈
    const nodes = parseChildren(context, [])

    // 解析器返回 Root 根节点
    return {
        type: 'Root',
        // 使用nodes 作为根节点的children
        children: nodes
    }
}


// const ast = parse('<div id="foo" v-show="display"></div>')
// const ast = parse('<div>Text</div>')
// const ast = parse(`<div><!-- comments --></div>`)
const ast = parse(`<div><!-- comments -->{{ bar }}</div>`)
// const ast = parse(`<div>foo{{ bar }}baz</div>`)
console.log('[48;5;220m [ ast ]-263 [0m', JSON.stringify(ast, null, 2))


